package com.example.demo.config;

import com.example.demo.handler.MyAuthenticationFailureHandler;
import com.example.demo.handler.MyAuthenticationSuccessHandler;
import com.example.demo.handler.MyLogoutHandler;
import com.example.demo.handler.MyLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class MySecurityConfiguration extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private UserDetailsService myUserDetailsService;
    /**
     *创建一个SecurityFilterChain对象
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
        //创建匿名内部类对象,实现认证相关配置
        //登录页面和登录请求地址默认不校验权限.不校验认证的.
        //建议把登录页面和登录请求都设置在授权配置中
        //Security框架中,登录认证成功后,默认重定向到最近一次访问地址
        //如果最近访问地址是/ 或 登陆页面地址,则重定向到/ .
        //认证失败后默认重定向到登录页面?error
        //此默认信息在AbstractAuthenticationFilterConfigurer类型中定义的
        Customizer<FormLoginConfigurer<HttpSecurity>> customizer = new Customizer<FormLoginConfigurer<HttpSecurity>>() {
            @Override
            public void customize(FormLoginConfigurer<HttpSecurity> configurer) {
                //具体的认证配置
                configurer.loginPage("/login")//设置登录页面访问地址.默认是/login,必须是get请求,自定义后提供控制器和视图
                        .loginProcessingUrl("/userLogin")//设置执行登录请求地址,此请求必须是post.默认是/login
                        .usernameParameter("name")//设置请求参数名称
                        .passwordParameter("pwd")
                        .defaultSuccessUrl("/main")//设置认证成功后默认的跳转地址,仅在直接访问登录页面时生效
                        .successForwardUrl("/main")//设置认证成功后转发地址.全局生效
                        .successHandler(new MyAuthenticationSuccessHandler("/main",true))//设置认证成功后的代码逻辑 参数是AuthenticationSuccessHandler类型对象
                        .failureUrl("/loginFail")//默认认证失败后跳转地址
                        .failureForwardUrl("/loginFail")//认证失败后转发地址
                        .failureHandler(new MyAuthenticationFailureHandler("/loginFail"));//认证失败后处理代码逻辑,AuthenticationFailureHandler
            }
        };
        //设置认证配置
        security.formLogin(customizer);

        Customizer<RememberMeConfigurer<HttpSecurity>> rememberMe = new Customizer<RememberMeConfigurer<HttpSecurity>>() {
            @Override
            public void customize(RememberMeConfigurer<HttpSecurity> configurer) {

                configurer.tokenRepository(persistentTokenRepository())//设置保存记住我数据的具体对象
                        .rememberMeParameter("remember-me")//请求参数中,记住我参数名,默认remember-me
//                        .rememberMeCookieName("DEMO_REM")//服务器端把登陆数据保存在数据库,客户端通过cookie记录数据库唯一信息 默认remember-me
//                        .rememberMeCookieDomain("localhost")//cookie的domain,默认自动解析
                        .tokenValiditySeconds(18000)//cookie保存时长,单位是秒
                        .userDetailsService(myUserDetailsService);//设置自定义UserDetailsService接口实现对象
            }
        };
        //配置rememberMe相关信息
        security.rememberMe(rememberMe);

        Customizer<LogoutConfigurer<HttpSecurity>> logout = new Customizer<LogoutConfigurer<HttpSecurity>>() {
            @Override
            public void customize(LogoutConfigurer<HttpSecurity> configurer) {
                configurer.logoutUrl("/logout")//退出登录请求地址默认是/logout
                        .logoutSuccessUrl("/login")//退出登录成功后默认跳转地址,登录页面地址?logout
                        .logoutSuccessHandler(new MyLogoutSuccessHandler())//设置退出登录成功后代码逻辑.必须是LogoutSuccessHandler类型对象
                        .addLogoutHandler(new MyLogoutHandler());//增加额外的退出成功后的处理代码 是LogoutHandler类型对象
            }
        };
        //退出登录配置
        security.logout(logout);


        //授权配置
        /**
         * requestMatchers方法有多个重载overload方法,常用的是
         *  requestMatchers(String...urls)针对参数请求地址做映射匹配
         *  requestMatchers(Method method,String...urls)针对参数请求地址+固定请求方式做映射匹配
         *      HttpMethod.GET .POST .PUT .DELETE等,仅能指定一种
         * anyRequest代表一切请求地址映射,相当于requestMatchers("/**") 此方法必须在最后调用
         */
        security.authorizeRequests()
                .requestMatchers("/login","/userLogin").permitAll() //访问/login地址时,不做认证授权访问
//                .requestMatchers(HttpMethod.POST,"/js/**").permitAll()//设置请求地址是/js/**时,可以随意访问
//                .requestMatchers("/js/**").hasRole("超级管理员")
//                .requestMatchers("/js/**").hasAnyRole("超级管理员","普通用户")
//                .requestMatchers("/js/**").hasAuthority("user:add")
                .requestMatchers("/js/**").hasAnyAuthority("user:add","login:login")
//                .requestMatchers("/js/**").hasIpAddress("127.0.0.1")
                .anyRequest().authenticated(); //访问其他所有地址时,必须认证成功后可以访问
        //关闭CSRF功能.
        security.csrf().disable();
        return security.build();
    }

    /**
     * 创建一个bean对象,使用Security框架提供的默认实现类型即可
     * JdbcTokenRepositoryImpl 基于DataSource数据源连接池,访问指定的数据库
     * 把认证成功的,需要rememberMe的用户数据保存到数据库
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        //new对象
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();

        repository.setDataSource(dataSource);
        //初始化参数,仅第一次启动当前项目时设置为true,后续修改为false
        //如果设置true,启动时,自动连接数据库,创建表格,保存rememberMe数据的
        repository.setCreateTableOnStartup(false);
        return repository;
    }

    /**
     * 创建一个PasswordEncoder类型的bean对象
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        //参数为强度 4-31
        return new BCryptPasswordEncoder(4);
    }
}
